package com.billy.android.register

import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import org.apache.commons.io.FileUtils
import org.gradle.api.DefaultTask
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileType
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.work.ChangeType
import org.gradle.work.Incremental
import org.gradle.work.InputChanges

import javax.inject.Inject

/**
 * AGP 8+ Task for AutoRegister plugin.
 * 替代原来的 RegisterTransform
 */
@CacheableTask
abstract class AutoRegisterTask extends DefaultTask {

    // 输入：所有 Jar 文件
    @Incremental
    @InputFiles
    @PathSensitive(PathSensitivity.RELATIVE)
    abstract ListProperty<RegularFile> getInputJars()

    // 输入：所有类文件目录
    @Incremental
    @InputFiles
    @PathSensitive(PathSensitivity.RELATIVE)
    abstract ListProperty<Directory> getInputDirs()

    // 输出：处理后的文件目录
    @OutputDirectory
    abstract DirectoryProperty getOutput()

    // 配置信息
    @Internal
    abstract Property<AutoRegisterConfig> getConfig()

    AutoRegisterTask() {
        // 确保这个任务总是运行
        outputs.upToDateWhen { false }
    }

    @TaskAction
    void execute(InputChanges inputChanges) {
        long startTime = System.currentTimeMillis()
        logger.warn("start auto-register task...")
        
        def config = getConfig().get()
        config.reset()
        logger.warn(config.toString())
        
        // 确定是否是增量构建
        boolean isIncremental = inputChanges.incremental
        if (!isIncremental) {
            // 清理输出目录
            if (getOutput().get().asFile.exists()) {
                getOutput().get().asFile.deleteDir()
                getOutput().get().asFile.mkdirs()
            }
        }
        
        boolean leftSlash = File.separator == '/'
        
        // 缓存处理
        def cacheEnabled = config.cacheEnabled
        println("auto-register-----------isIncremental:${isIncremental}--------config.cacheEnabled:${cacheEnabled}--------------------\n")
        
        File cacheFile = null
        Map<String, ScanJarHarvest> cacheMap = null
        Gson gson = null
        
        if (cacheEnabled) { // 开启了缓存
            gson = new Gson()
            cacheFile = AutoRegisterHelper.getRegisterCacheFile(project)
            if (!isIncremental && cacheFile.exists())
                cacheFile.delete()
            cacheMap = AutoRegisterHelper.readToMap(cacheFile, new TypeToken<HashMap<String, ScanJarHarvest>>() {
            }.getType())
        }
        
        CodeScanProcessor scanProcessor = new CodeScanProcessor(config.list, cacheMap)
        
        // 处理 JAR 输入
        processJars(inputChanges, scanProcessor, isIncremental, cacheMap)
        
        // 处理目录输入
        processDirectories(inputChanges, scanProcessor, isIncremental, leftSlash)
        
        // 保存缓存
        if (cacheMap != null && cacheFile && gson) {
            def json = gson.toJson(cacheMap)
            AutoRegisterHelper.cacheRegisterHarvest(cacheFile, json)
        }
        
        def scanFinishTime = System.currentTimeMillis()
        project.logger.error("register scan all class cost time: " + (scanFinishTime - startTime) + " ms")
        
        // 执行代码注入
        injectCode(config)
        
        def finishTime = System.currentTimeMillis()
        project.logger.error("register insert code cost time: " + (finishTime - scanFinishTime) + " ms")
        project.logger.error("register cost time: " + (finishTime - startTime) + " ms")
    }
    
    private void processJars(InputChanges inputChanges, CodeScanProcessor scanProcessor, 
                            boolean isIncremental, Map<String, ScanJarHarvest> cacheMap) {
        // 创建输出目录
        File outputDir = getOutput().get().asFile
        if (!outputDir.exists()) {
            outputDir.mkdirs()
        }
        
        // 如果是增量构建，处理变更的 JAR
        if (isIncremental) {
            inputChanges.getFileChanges(getInputJars()).each { change ->
                if (change.changeType == ChangeType.MODIFIED || change.changeType == ChangeType.ADDED) {
                    File jarFile = change.file
                    // 输出文件名采用 md5 命名避免重名
                    String md5 = org.apache.commons.codec.digest.DigestUtils.md5Hex(jarFile.absolutePath)
                    String destName = jarFile.name
                    if (destName.endsWith(".jar")) {
                        destName = destName.substring(0, destName.length() - 4)
                    }
                    File destFile = new File(outputDir, "${destName}_${md5}.jar")
                    
                    if (cacheMap) {
                        cacheMap.remove(jarFile.absolutePath) // 移除已变更的缓存
                    }
                    
                    processJarFile(jarFile, destFile, scanProcessor)
                } else if (change.changeType == ChangeType.REMOVED) {
                    // 文件被删除，从缓存中移除
                    if (cacheMap) {
                        cacheMap.remove(change.file.absolutePath)
                    }
                }
            }
        } else {
            // 非增量构建，处理所有 JAR
            getInputJars().get().each { jarFile ->
                // 输出文件名采用 md5 命名避免重名
                String md5 = org.apache.commons.codec.digest.DigestUtils.md5Hex(jarFile.asFile.absolutePath)
                String destName = jarFile.asFile.name
                if (destName.endsWith(".jar")) {
                    destName = destName.substring(0, destName.length() - 4)
                }
                File destFile = new File(outputDir, "${destName}_${md5}.jar")
                
                processJarFile(jarFile.asFile, destFile, scanProcessor)
            }
        }
    }
    
    private void processJarFile(File srcJar, File destJar, CodeScanProcessor scanProcessor) {
        long time = System.currentTimeMillis();
        if (!scanProcessor.scanJar(srcJar, destJar) // 直接读取了缓存，没有执行实际的扫描
                && !scanProcessor.isCachedJarContainsInitClass(srcJar.absolutePath)) {
            // 不需要执行文件复制，直接返回
            return
        }
        println "auto-register cost time: " + (System.currentTimeMillis() - time) + " ms to scan jar file:" + destJar.absolutePath
        // 复制 jar 文件到输出目录
        FileUtils.copyFile(srcJar, destJar)
    }
    
    private void processDirectories(InputChanges inputChanges, CodeScanProcessor scanProcessor, 
                                   boolean isIncremental, boolean leftSlash) {
        File outputDir = getOutput().get().asFile
        
        if (isIncremental) {
            inputChanges.getFileChanges(getInputDirs()).each { change ->
                if (change.changeType == ChangeType.MODIFIED || change.changeType == ChangeType.ADDED) {
                    File dirFile = change.file
                    if (dirFile.isDirectory()) {
                        processDirectory(dirFile, outputDir, scanProcessor, leftSlash)
                    } else if (dirFile.isFile() && dirFile.name.endsWith('.class')) {
                        String relativePath = getRelativePath(dirFile, change.normalizedPath)
                        if (scanProcessor.shouldProcessClass(relativePath)) {
                            scanProcessor.scanClass(dirFile)
                        }
                        // 拷贝到输出目录
                        File targetFile = new File(outputDir, relativePath)
                        if (!targetFile.getParentFile().exists()) {
                            targetFile.getParentFile().mkdirs()
                        }
                        FileUtils.copyFile(dirFile, targetFile)
                    }
                }
            }
        } else {
            // 非增量构建，处理所有目录
            getInputDirs().get().each { dir ->
                processDirectory(dir.asFile, outputDir, scanProcessor, leftSlash)
            }
        }
    }
    
    private void processDirectory(File dirFile, File outputDir, CodeScanProcessor scanProcessor, boolean leftSlash) {
        long dirTime = System.currentTimeMillis();
        String root = dirFile.absolutePath
        if (!root.endsWith(File.separator))
            root += File.separator
            
        // 拷贝目录结构
        if (dirFile.exists()) {
            File destDir = new File(outputDir, dirFile.name)
            if (!destDir.exists()) {
                destDir.mkdirs()
            }
            
            // 遍历目录下的每个文件
            dirFile.eachFileRecurse { File file ->
                if (file.isFile()) {
                    def path = file.absolutePath.replace(root, '')
                    def entryName = path
                    if (!leftSlash) {
                        entryName = entryName.replaceAll("\\\\", "/")
                    }
                    
                    // 检查是否需要处理
                    if (scanProcessor.shouldProcessClass(entryName)) {
                        scanProcessor.scanClass(file)
                    }
                    
                    // 拷贝文件到输出目录
                    File targetFile = new File(outputDir, path)
                    if (!targetFile.getParentFile().exists()) {
                        targetFile.getParentFile().mkdirs()
                    }
                    FileUtils.copyFile(file, targetFile)
                }
            }
            
            long scanTime = System.currentTimeMillis();
            println "auto-register cost time: ${System.currentTimeMillis() - dirTime}, scan time: ${scanTime - dirTime}. path=${root}"
        }
    }
    
    private String getRelativePath(File file, String normalizedPath) {
        // 从 normalized path 中提取相对路径
        return normalizedPath
    }
    
    private void injectCode(AutoRegisterConfig config) {
        config.list.each { ext ->
            if (ext.fileContainsInitClass) {
                println('')
                println("insert register code to file:" + ext.fileContainsInitClass.absolutePath)
                if (ext.classList.isEmpty()) {
                    project.logger.error("No class implements found for interface:" + ext.interfaceName)
                } else {
                    ext.classList.each {
                        println(it)
                    }
                    CodeInsertProcessor.insertInitCodeTo(ext)
                }
            } else {
                project.logger.error("The specified register class not found:" + ext.registerClassName)
            }
        }
    }
} 